unit LogReader;

interface

uses SysUtils, Classes, MuUtils;

procedure DoIt(const FileName: string; ErrorItems: TList);

implementation

uses LogErrors, LogLexer, LogData;

const
  STATE_INITIAL = 0;
  STATE_TYPE = 1;
  STATE_METHOD = 2;
  STATE_METHOD_WHERE = 3;

var
  State: Integer;

procedure ReadOption();
var
  Lang: string;
  Id, Value: string;
begin
  Lang := '';

  ParseCheck(TakeId(), ERROR_OPTION_NAME_OR_FOR_EXPECTED);

  if LogLexer.Value = 'FOR' then
  begin
    ParseCheck(TakeWord(), ERROR_OPTION_LANG_NAME_EXPECTED);
    Lang := LogLexer.Value;
    ParseCheck(TakeId(), ERROR_OPTION_NAME_OR_FOR_EXPECTED);
  end;

  Id := LogLexer.Value;

  ParseCheck(TakeSymbol(), ERROR_OPTION_EQ_EXPECTED);
  ParseCheck(LogLexer.Value = '=', ERROR_OPTION_EQ_EXPECTED);

  TakeTail();
  Value := LogLexer.Value;

  LogData.AddOption(Id, Value, Lang);
end;

procedure ReadType();
begin
  ParseCheck(TakeId(), ERROR_TYPE_NAME_EXPECTED);
  LogData.AddTypedef(LogLexer.Value);
  ParseCheck(LogLexer.Eol(), ERROR_TYPE_NO_EOL);
  State := STATE_TYPE;
end;

procedure ReadFiledType(var VarType: TVarType; ErrorCode: Integer);
var
  ConstType: Integer;
begin
  VarType.Id := '';
  VarType.Len := -1;
  ParseCheck(TakeId(), ErrorCode);
  VarType.Id := LogLexer.Value;

  if LookupSymbol() = False then Exit;
  if LogLexer.Value <> '[' then Exit;

  if not TakeSymbol() then InternalError('ReadFiledType: successful LookupSymbol but TakeSymbol fails.');
  ConstType := LookupConst();
  ParseCheck(ConstType = CONST_INTEGER, ERROR_FIELD_TYPE_SIZE_EXPECTED);
  if TakeConst() <> CONST_INTEGER then InternalError('LookupConst returns CONST_INTEGER, but TakeConst return something else.');
  VarType.Len := StrToIntDef(LogLexer.Value, -1);
  ParseCheck(VarType.Len >= 0, ERROR_FIELD_TYPE_SIZE_EXPECTED);
  ParseCheck(TakeSymbol(), ERROR_FIELD_TYPE_SIZE_RBRACKER_EXPECTED);
  ParseCheck(LogLexer.Value = ']', ERROR_FIELD_TYPE_SIZE_RBRACKER_EXPECTED);
end;

procedure ReadField(Indexed: Boolean);
var
  Id: string;
  VarType: TVarType;
begin
  ParseCheck(State = STATE_TYPE, ERROR_FIELD_IS_NOT_INSIDE_TYPE);
  if Indexed then
  begin
    ParseCheck(TakeId(), ERROR_FIELD_AFTER_INDEXED_EXPECTED);
    ParseCheck(LogLexer.Value = 'FIELD', ERROR_FIELD_AFTER_INDEXED_EXPECTED);
  end;

  ParseCheck(TakeId(), ERROR_FIELD_ID_EXPECTED);
  Id := LogLexer.Value;

  ParseCheck(TakeSymbol(), ERROR_FIELD_COLON_EXPECTED);
  ParseCheck(LogLexer.Value = ':', ERROR_FIELD_COLON_EXPECTED);

  ReadFiledType(VarType, ERROR_FIELD_TYPE_EXPECTED);

  VerifyType(VarType);
  LogData.AddField(Id, VarType, Indexed);
  if VarType.ErrorCode <> 0 then
    ParseError(VarType.ErrorCode);

  ParseCheck(LogLexer.Eol(), ERROR_FIELD_NO_EOL);
end;

procedure ProcessLine();

  function GetSupportedKeywordsByState(): string;
  begin
    case State of
      STATE_INITIAL: Result := 'OPTION or TYPE';
      STATE_TYPE: Result := 'OPTION, TYPE, FIELD or INDEXED';
      else InternalError('Unsupported state');
    end;
  end;

begin
  ParseCheck(TakeId(), ERROR_KEYWORD_EXPECTED, ['OPTION, TYPE']);
  if LogLexer.Value = 'OPTION' then ReadOption() else
  if LogLexer.Value = 'TYPE' then ReadType() else
  if LogLexer.Value = 'FIELD' then ReadField(False) else
  if LogLexer.Value = 'INDEXED' then ReadField(True) else
    ParseError(ERROR_KEYWORD_EXPECTED, ['OPTION']);
end;

{$I-}
procedure DoIt(const FileName: string; ErrorItems: TList);
var
  F: TextFile;
  CurrentLine: string;
begin
  try

    System.Assign(F, FileName);
    System.Reset(F);
    if IOResult <> 0 then
      FatalError(FATAL_OPEN_FILE_FAILS, [FileName]);

    try

      State := STATE_INITIAL;
      LogLexer.Reset();
      while not EOF(F) do
      begin

        ReadLn(F, CurrentLine);
        if IOResult <> 0 then
          FatalError(FATAL_READ_FILE_FAILS, [FileName]);

        LogLexer.NewLine(CurrentLine);

        try
          if not LogLexer.Eol() then
            ProcessLine();
        except
          on E: EParseError do
            ErrorItems.Add(E.CreateErrorItem());
        end;
      end;

    finally
      System.CloseFile(F);
    end;

  except
    on E: EFatalError do
      ErrorItems.Add(E.CreateErrorItem());
  end;
end;

end.